React / Redux Best Practices

Here are three tips each on React and Redux that will help build more idiomatic, maintainable codebases.

Summary

React:

  1. Minimize use of Component State
  2. Leverage Latest Javascript Features
  3. Calculate Everything In render()

Redux:

  1. Use Memoized Selectors
  2. Bind Action Creators
  3. Compose Your Reducers

React

1) Minimize Use of Component State

Especially when used in tandem with a state management library like Redux, React components have very little use for internal state. That being said, knowing when to use internal state and when to lift something up as props is one of the more difficult and arguably subjective aspects of React development. Here are a few things to think about:

Consider the example of a modal window with multiple tabs. It is perfectly acceptable and idiomatic to store the name of the active tab in state:

constructor() {
  super();
  this.state = {
    selectedTab: 'All'
  };
}

To update the selected tab, each tab might have a click listener with logic like this:

  handleClick = tabName => this.setState({ selectedTab: tabName });

This is a fine setup for now, but later we get a new feature request from users. The modal needs to open to a specific tab depending on where the user clicked. We decide to pass down the selected tab as a prop, so it can be accessed by the modal internally. Now our constructor looks like this:

  constructor(props) {
    super(props);
    this.state = {
      selectedTab: props.selectedTab || 'All'
    };
  }

Now the setState click listener will still work for changing tabs, but we had better hope that the component is never re-rendered, or it will go back to rendering based on the selectedTab prop. Now we need to leverage an additional lifecycle method, componentWillReceiveProps, in order to update the component's internal state.

  componentWillReceiveProps(newProps) {
    const { selectedTab } = newProps;
    if (selectedTab) {
      this.setState({ selectedTab });
    }
  }

Our modal component is getting pretty complicated, all in service of what seems like fairly typical modal behavior. This has all happened because we have effectively synchronized the state between a child component and its parent. Don't do this. We solve this problem by removing state from the component entirely, and storing the selected tab in the parent component. Now our modal component looks like this:

const Modal = ({ selectedTab, updateTab }) =>
  <div className="modal-body">
    <div className="tabs">
      <button className="tab" onClick={updateTab}>
        All
      </button>
      ...
    </div>
    <div className="tab-content">...</div>
  </div>;

The entire component is just a function. The currently selected tab is passed down as a prop, and the updateTab prop is a function that updates the parent component's state. Now the modal's appearance can be updated simply by passing down a new prop.

2) Leverage Latest Javascript Features

Next-gen JavaScript features make React development a breeze. Here are a few before-and-after snippets that show the power of ESNext / Babel compilation.

Generating An Array of Child Components

The old way (for loop, method binding in constructor):

  constructor() {
    super();
    this.renderTodoList.bind(this);
  }

  renderTodoList(arrayOfTodos) {
    var arrayOfComponents = [];
    for (i = 0; i < arrayOfTodos.length; i++) {
      var thisTodo = arrayOfTodos[i];
      arrayOfComponents.push(<Todo key={thisTodo.id} text={thisTodo.text} />);
    }
    return arrayOfComponents;
  }

The new way (ES6 arrows, inherent binding, map utility)

  renderTodoList = arrayOfTodos =>
    arrayOfTodos.map(todo => <Todo key={todo.id} {...todo} />);

Pass Down Props To Child

Old way (constantly reference this.props)

  render() {
    return (
      <TodoList
        type={this.props.todoType}
        todos={this.props.todos}
        visible={!this.props.isHidden}
      />
    );
  }

New way (more DRY)

  render() {
    const { todoType, todos, isHidden } = this.props;
    return <TodoList type={todoType} todos={todos} visible={!isHidden} />;
  }

In fact, with a little manipulation of prop names it could even be:

const Todo = props => <TodoList {...props} />

Dynamic CSS classNames (String Concatenation)

Old way (hard to read)

const myClassyComponent = <div className={'modal ' + (this.props.hidden ? 'hidden' : '') + (this.props.disabled ? 'disabled' : '')} />

New way (very clear)

const { hidden, disabled } = this.props;
const hiddenClass = hidden ? 'hidden' : '';
const disabledClass = disabled ? 'disabled' : '';
const myClassyComponent = <div className={`modal ${hiddenClass} ${disabledClass}`} />;

Template strings also come in handy for networking code:

const apiFetch = (path, hostName = 'http://localhost:3001') =>
  fetch(`${hostName}/${path}`).then(res => res.json());

3) Calculate Everything In render()

If you follow the guidance above in regard to component state, you shouldn't run into this issue, but it's still worth a mention. Do not store derived data in component state! Any calculations or conditionals should be evaluated at the last minute, in the component's render function. A common mistake for less-experienced React devs is to do something like this:

EXAMPLE OF WHAT NOT TO DO

  constructor(props) {
    const itemTotal = this.props.items.reduce(
      (sum, thisItem) => sum + thisItem.total,
      0
    );
    this.state = { itemTotal };
  }

  render() {
    return (
      <div className="itemTotal">
        {this.state.itemTotal}
      </div>
    );
  }

Saving the item total in state seems like the right thing to do, because the total is the value that will eventually be rendered out. However, the amount of code required to keep this value in sync with the component's props makes it very difficult to maintain. Additionally, what happens if we need to get an average item total, or a total of a filtered subset of items? Our component logic would quickly become a twisted mess.

Better instead to factor out a helper function that only renders the value when it's needed.

Better

  getItemTotal = items =>
    items.reduce((sum, thisItem) => sum + thisItem.total, 0);

  render() {
    return (
      <div className="itemTotal">
        {this.getItemTotal(this.props.items)}
      </div>
    );
  }

Notice also that our getItemTotal can act on any array of items and is not tied to this.props. In the future, it would be easy to arrive at the total of only a subset of items without changing the method.

  getItemTotal = items =>
    items.reduce((sum, thisItem) => sum + thisItem.total, 0);

  render() {
    const activeItems = this.props.items.filter(item => item.active);
    return (
      <div className="activeItemTotal">
        {this.getItemTotal(activeItems)}
      </div>
    );
  }

Redux

Redux is a powerful state management library that leverages the Flux architecture pattern. Taking cues from functional programming languages like Haskell and the ML family, Redux allows the creation of deterministic UI components, obfuscating the concept of application state. In this section I'll address three ways to turbocharge your Redux setup.

1) Use Memoized Selectors

Redux's action creators, reducers, and mapStateToProps functions should be pure functions - they should input and output only data with no side-effects. Inherit in this definition is the idea that a pure function will always return the same output given the same input. So when we're building applications with pure functions, and we know that the same input will always yield the same output, why not save the output of a function given a certain input? Why go to the trouble of calculating a return value again if we know it will be the same as the last time we gave the function a certain input? This idea is called memoization, and it is leveraged to great advantage by the npm package reselect.

reselect allows users to create memoized "selectors", functions that calculate a derived value based on input and memoize their output so that the next time the function is called with the same input, it doesn't actually need to calculate anything again. Let's take a look (there are also several good examples of selectors on the reselect Github page).

Using the above example of getItemTotal, we know that each time our component receives a new set of props, the item total will be calculated again. The reduce function has a computational complexity of O(n), where n is the length of the item array, and the filter function also has a computational complexity of O(n), which makes the computational complexity for this component's render method O(n^2). However, let's imagine that the items live elsewhere, in a redux store, and that we want to pass just the filtered items into this component. We might do so with a selector.

/// mapStateToProps.js

import { createSelector } from 'reselect';

const itemSelector = state => state.items;

const activeItems = createSelector(itemSelector, items =>
  items.filter(item => item.active)
);

const activeItemTotal = createSelector(activeItems, items =>
  items.reduce((sum, thisItem) => sum + thisItem.total, 0)
);

export default state => ({ activeItemTotal: activeItemTotal(state) });

Now the computationally complex operation will only happen if the input changes (i.e., the item list has changed). We've also made our component logic more simple- this item subtotal widget is now a one-line arrow function: const SubTotalWidget = ({activeItemTotal}) => <div className="subtotalWidget">activeItemTotal</div>;

2) Bind Action Creators

Redux's event dispatching system is the heart of its state management functionality. However, it can be tedious to pass down the dispatch function as a prop to every component that needs to dispatch an action. Additionally, it is anathema to the idea of separation of concerns. Ideally only container components would know the mechanism by which actions are dispatched, and the child components would simply dispatch them. Let's refactor two components to accomplish this separation. In this example, Container is a component that is connected to a Redux store, and PurchaseButton is a styled button that handles the actual purchase of an item.

// Container.jsx
  ...
  render() {
    return (
      <PurchaseButton
        itemId={this.props.selectedItem.Id}
        dispatch={this.props.dispatch}
      />
    );
  }

// PurchaseButton.jsx

import { purchaseItem } from '../actions/itemActions';

const PurchaseButton = ({ itemId, dispatch, active }) =>
  <button
    className={`button ${active ? 'active' : 'inactive'}`}
    onClick={() => dispatch(purchaseItem(itemId))}
  />;

It doesn't feel quite right for a button to have so much knowledge about the inner workings of our app. A button has one job: listen for clicks and fire a callback. Let's refactor this setup by binding dispatch to the action creator.

// Container.jsx

import { bindActionCreators } from 'redux';
import { purchaseItem } from '../itemActions';

  ...
  render() {
    const clickHandler = bindActionCreators(purchaseItem, this.props.dispatch);
    return (
      <PurchaseButton
        itemId={this.props.selectedItem.id}
        handleClick={clickHandler}
      />
    );
  }

// PurchaseButton.jsx

const PurchaseButton = ({ itemId, clickHandler, active }) =>
  <button
    className={`button ${active ? 'active' : 'inactive'}`}
    onClick={() => clickHandler(itemId)}
  />;

Being intentional with where you import action creators makes it much easier to maintain your code in the long run.

3) Compose Your Reducers

In crafting reducers for your application, you may find that they balloon in size as more action types are added, especially if you are careful to maintain data immutability. However, it is possible to extract multiple reducers within a reducer file to make your code more readable and maintainable. Take this ManualTradeTicket reducer for example:

const selectManualTicket = (state, action) => {
  const { tradeActivity } = action;
  const { administrator, transactionID } = tradeActivity;
  let selectedTradeIds;
  if (state.selectedTradeIds[administrator]) {
    if (state.selectedTradeIds[administrator].includes(transactionID)) {
      selectedTradeIds = {
        ...state.selectedTradeIds,
        [administrator]: state.selectedTradeIds[administrator].filter(
          tradeId => tradeId !== transactionID
        )
      };
    } else {
      selectedTradeIds = {
        ...state.selectedTradeIds,
        [administrator]: [
          ...state.selectedTradeIds[administrator],
          transactionID
        ]
      };
    }
  } else {
    selectedTradeIds = {
      ...state.selectedTradeIds,
      [administrator]: [transactionID]
    };
  }
  return { ...state, selectedTradeIds };
};

const manualTickets = (state = initialState, action) => {
  switch (action.type) {
    case SELECT_MANUAL_TICKET:
      return selectManualTicket(state, action);
    case SELECT_ALL_MANUAL_TICKETS:
      return selectAllManualTickets(state, action);
    case CLEAR_ALL_SELECTED_MANUAL_TICKETS:
      return clearAllSelectedManualTickets(state, action);
    default:
      return state;
  }
};

export default manualTickets;

Extracting the logic for selectManualTicket() into another reducer makes the reducer itself much easier to read.